﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.IO;

namespace Cepheus
{
    public class DeviceChangedAgent : NativeWindow
    {
        #region Win32 imports

        const int WM_DEVICECHANGE = 0x219;
        const int DBT_DEVICEARRIVAL = 0x8000;
        const int DBT_DEVICEREMOVECOMPLETE = 0x8004;
        const int DBT_DEVTYP_VOLUME = 2;

        [StructLayout(LayoutKind.Sequential)]
        private struct DEV_BROADCAST_HDR
        {
            public int size;
            public int devicetype;
            public int reserved;
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct DEV_BROADCAST_VOLUME
        {
            public DEV_BROADCAST_HDR Header;
            public int UnitMask;
            public short Flags;
        }

        [DllImport("kernel32.dll")]
        private static extern long GetVolumeInformation(string PathName, StringBuilder VolumeNameBuffer, UInt32 VolumeNameSize, ref UInt32 VolumeSerialNumber, ref UInt32 MaximumComponentLength, ref UInt32 FileSystemFlags, StringBuilder FileSystemNameBuffer, UInt32 FileSystemNameSize);


        #endregion

        #region events

        public event EventHandler<DeviceChangedEventArgs> DeviceChanged;
        public event EventHandler<ErrorEventArgs> ErrorOccured;

        protected virtual void OnDeviceChanged(object sender, DeviceChangedEventArgs e)
        {
            EventHandler<DeviceChangedEventArgs> deviceChanged = DeviceChanged;
            if (deviceChanged != null)
            {
                deviceChanged(sender, e);
            }
        }

        protected virtual void OnErrorOccured(object sender, ErrorEventArgs e)
        {
            EventHandler<ErrorEventArgs> errorOccured = ErrorOccured;
            if (errorOccured != null)
            {
                errorOccured(sender, e);
            }
        }

        #endregion


        public DeviceChangedAgent(IWin32Window parentWindow)
        {
            this.AssignHandle(parentWindow.Handle);
        }

        private char GetVolumeLetter(int Mask)
        {
            int i = 0;
            for (i = 0; i < 26; ++i)
            {
                if ((Mask & 0x1) == 0x1) break;
                Mask = Mask >> 1;
            }
            return (char)(Convert.ToChar(i) + 'A');
        }

        /// <summary>
        /// Returns the serial of the specified drive path.
        /// </summary>
        /// <param name="drivePath">Format: X:\</param>
        /// <returns></returns>
        public static string GetVolumeSerial(string drivePath)
        {
            uint serNum = 0;
            uint maxCompLen = 0;
            StringBuilder volLabel = new StringBuilder(256);
            UInt32 volFlags = new UInt32();
            StringBuilder fSName = new StringBuilder(256);
            GetVolumeInformation(drivePath, volLabel, (UInt32)volLabel.Capacity, ref serNum, ref maxCompLen, ref volFlags, fSName, (UInt32)fSName.Capacity);

            return serNum.ToString();
        }

        protected override void WndProc(ref Message m)
        {
            if (m.Msg == WM_DEVICECHANGE && (m.WParam.ToInt32() == DBT_DEVICEARRIVAL || m.WParam.ToInt32() == DBT_DEVICEREMOVECOMPLETE))
            {
                DEV_BROADCAST_HDR Hdr = new DEV_BROADCAST_HDR();
                DEV_BROADCAST_VOLUME Volume = new DEV_BROADCAST_VOLUME();

                try
                {
                    Hdr = (DEV_BROADCAST_HDR)Marshal.PtrToStructure(m.LParam, Hdr.GetType());

                    if (Hdr.devicetype == DBT_DEVTYP_VOLUME)
                    {
                        Volume = (DEV_BROADCAST_VOLUME)Marshal.PtrToStructure(m.LParam, Volume.GetType());
                    }
                }
                catch (Exception ex)
                {
                    OnErrorOccured(this, new ErrorEventArgs(ex));
                }

                if (m.WParam.ToInt32() == DBT_DEVICEARRIVAL)
                {
                    DeviceChangedEventArgs args = new DeviceChangedEventArgs();
                    args.DeviceChangedAction = DeviceChangedAction.Arrival;
                    args.VolumeLetter = GetVolumeLetter(Volume.UnitMask).ToString();
                    args.VolumeSerial = GetVolumeSerial(args.VolumeLetter + @":\");

                    OnDeviceChanged(this, args);
                }
                else if (m.WParam.ToInt32() == DBT_DEVICEREMOVECOMPLETE)
                {
                    DeviceChangedEventArgs args = new DeviceChangedEventArgs();
                    args.DeviceChangedAction = DeviceChangedAction.RemoveComplete;
                    args.VolumeLetter = GetVolumeLetter(Volume.UnitMask).ToString();
                    args.VolumeSerial = GetVolumeSerial(args.VolumeLetter + @":\");

                    OnDeviceChanged(this, args);
                }
            }

            base.WndProc(ref m);
        }
    }
}
